package dogapm

import (
	"context"
	"dogapm/internal"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"time"
)

const (
	grpcClientTracerName = "grpc.client"
	peerApp              = "peerApp"
	peerHost             = "peerHost"
)

type GrpcClient struct {
	*grpc.ClientConn
}

func NewGrpcClient(addr string) *GrpcClient {
	conn, err := grpc.NewClient(addr,
		grpc.WithUnaryInterceptor(unaryInterceptor()),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		panic(err)
	}
	return &GrpcClient{ClientConn: conn}
}

func unaryInterceptor() grpc.UnaryClientInterceptor {
	tracer := otel.GetTracerProvider().Tracer(grpcClientTracerName)
	return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		ctx, span := tracer.Start(ctx, method, trace.WithSpanKind(trace.SpanKindClient))
		start := time.Now()
		defer func() {
			span.SetAttributes(attribute.Int64("grpc.duration", time.Now().Sub(start).Milliseconds()))
			span.End()
		}()
		md, ok := metadata.FromOutgoingContext(ctx)
		if !ok {
			md = metadata.MD{}
		}
		md.Set(peerApp, internal.BuildInfo.AppName())
		md.Set(peerHost, internal.BuildInfo.Hostname())
		otel.GetTextMapPropagator().Inject(ctx, &metadataSupplier{
			metadata: &md,
		})
		ctx = metadata.NewOutgoingContext(ctx, md)
		err := invoker(ctx, method, req, reply, cc, opts...)
		if err != nil {
			s, _ := status.FromError(err)
			span.RecordError(err, trace.WithStackTrace(true), trace.WithTimestamp(time.Now()))
			span.SetAttributes(attribute.Bool("error", true))
			span.SetAttributes(attribute.String("grpc.status_code", s.Code().String()))
			return err
		}
		return nil
	}
}
